﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.Objects;
using System.Linq;
using System.Linq.Expressions;

namespace Elo.Framework.Data.DataAccess.EF
{
    /// <summary>
    /// Oferece acesso e manipulação dos dados existentes na entidade com EntityFramework.
    /// </summary>
    /// <typeparam name="TContext">Tipo da entidade de contexto.</typeparam>
    /// <typeparam name="TSource">Tipo da entidade a ser manipulada. Ex: Empresas</typeparam>
    [Serializable, Description("Oferece acesso e manipulação dos dados existentes na entidade.")]
    public class Repository<TContext, TSource> : IRepository<TSource> where TSource : class 
    {

        /// <summary>
        /// Contexto do serviço de acesso a dados.
        /// </summary>
        [Description("Contexto do serviço de acesso a dados.")]
        public ObjectContext Context { get; set; }

        /// <summary>
        /// String de conexão para acesso a dados.
        /// </summary>
        [Description("String de conexão para acesso a dados.")]
        public String ConnectionString { get; set; }

        /// <summary>
        /// Inicializa o objeto de acesso a dados especificando o contexto de acesso a dados.
        /// </summary>
        /// <param name="connectionString">Representa o contexto do serviço de acesso a dados do ADO.Net</param>
        public Repository(string connectionString)
        {
            if (connectionString == null)
                throw new ArgumentNullException("connectionString");

            ConnectionString = connectionString;
            Context = (ObjectContext)Activator.CreateInstance(typeof(TContext));
        }

        /// <summary>
        /// Inicializa o objeto de acesso a dados especificando o contexto de acesso a dados.
        /// </summary>
        /// <param name="connectionString">Representa o contexto do serviço de acesso a dados do ADO.Net</param>
        /// <param name="context">Contexto de trabalho do EF, deve ser do tipo ObjectContext ou herdado.</param>
        public Repository(string connectionString, ObjectContext context)
        {
            if (connectionString == null)
                throw new ArgumentNullException("connectionString");

            ConnectionString = connectionString;
            Context = context;
        }

        /// <summary>
        /// Retorna um objeto da entidade manipulada conforme o valor do ID.
        /// </summary>
        /// <param name="where">Valores referente ao ID da entidade.</param>
        /// <returns>Entidade encontrada. Caso contrário retorna null.</returns>
        public TSource GetByID(Expression<Func<TSource, bool>> where)
        {
            try
            {
                var result = Context.CreateObjectSet<TSource>().SingleOrDefault(where);
                return result;
            }
            catch (Exception ex)
            {
                if (ex.InnerException != null)
                    throw new Exception(ex.InnerException.Message, ex);
                throw new Exception(ex.Message, ex);
            }
        }

        /// <summary>
        /// Retorna um objeto da entidade manipulada conforme o valor do ID.
        /// </summary>
        /// <param name="include">Lista de propriedades para expansão.</param>
        /// <param name="where">Valores referente ao ID da entidade.</param>
        /// <returns>Entidade encontrada. Caso contrário retorna null.</returns>
        public TSource GetByID(string include, Expression<Func<TSource, bool>> where)
        {
            try
            {
                var result = Context.CreateObjectSet<TSource>().Include(include).SingleOrDefault(where);
                return result;
            }
            catch (Exception ex)
            {
                if (ex.InnerException != null)
                    throw new Exception(ex.InnerException.Message, ex);
                throw new Exception(ex.Message, ex);
            }
        }

        /// <summary>
        /// Retorna todos objetos da entidade manipulada.
        /// </summary>
        /// <returns>Retorna uma lista dos objetos.</returns>
        public IList<TSource> GetAll()
        {
            try
            {
                IList<TSource> result = Context.CreateObjectSet<TSource>().ToList();
                return result;
            }
            catch (Exception ex)
            {
                if (ex.InnerException != null)
                    throw new Exception(ex.InnerException.Message, ex);
                throw new Exception(ex.Message, ex);
            }
        }

        /// <summary>
        /// Retorna todos objetos da entidade manipulada definindo as propriedade para expansão.
        /// </summary>
        /// <param name="include">Lista de propriedades para expansão.</param>
        /// <returns>Retorna uma lista dos objetos.</returns>
        public IList<TSource> GetAll(string include)
        {
            try
            {
                IList<TSource> result = Context.CreateObjectSet<TSource>()
                    .Include(include).ToList();
                return result;
            }
            catch (Exception ex)
            {
                if (ex.InnerException != null)
                    throw new Exception(ex.InnerException.Message, ex);
                throw new Exception(ex.Message, ex);
            }
        }

        /// <summary>
        /// Retorna todos objetos da entidade manipulada definindo as propriedade para expansão.
        /// </summary>
        /// <param name="include">Lista de propriedades para expansão.</param>
        /// <returns>Retorna uma lista dos objetos.</returns>
        public virtual IList<TSource> GetAll(params Expression<Func<TSource, object>>[] include)
        {
            try
            {

                if (include.Length == 0)
                    throw new Exception("Número de parametros inválido.");

                var expandString = include.Aggregate("", (current, item) => current + (item.Body.Type.Name + ","));

                //removenda a vírgula final
                expandString = expandString.Remove(expandString.LastIndexOf(','), 1);

                IList<TSource> result = Context.CreateObjectSet<TSource>()
                    .Include(expandString).ToList();
                return result;
            }
            catch (Exception ex)
            {
                if (ex.InnerException != null)
                    throw new Exception(ex.InnerException.Message, ex);
                throw new Exception(ex.Message, ex);
            }
        }

        /// <summary>
        /// Retorna os objetos da entidade manipulada de acordo a condição informada.
        /// </summary>
        /// <param name="predicate">Representa a condição a ser testada pela entidade</param>
        /// <returns>Retorna uma lista dos objetos</returns>
        public IList<TSource> GetWhere(Expression<Func<TSource, bool>> predicate)
        {
            try
            {
                IList<TSource> result = Context.CreateObjectSet<TSource>().Where(predicate).ToList();
                return result;
            }
            catch (Exception ex)
            {
                if (ex.InnerException != null)
                    throw new Exception(ex.InnerException.Message, ex);
                throw new Exception(ex.Message, ex);
            }
        }

        /// <summary>
        /// Retorna os objetos da entidade manipulada de acordo a condição informada.
        /// </summary>
        /// <param name="include">Lista de propriedades para expansão.</param>
        /// <param name="predicate">Representa a condição a ser testada pela entidade</param>
        /// <returns>Retorna uma lista dos objetos</returns>
        public IList<TSource> GetWhere(string include, Expression<Func<TSource, bool>> predicate)
        {
            try
            {
                IList<TSource> result = Context.CreateObjectSet<TSource>()
                    .Include(include).Where(predicate).ToList();
                return result;
            }
            catch (Exception ex)
            {
                if (ex.InnerException != null)
                    throw new Exception(ex.InnerException.Message, ex);
                throw new Exception(ex.Message, ex);
            }
        }

        /// <summary>
        /// Adiciona a entidade indicada.
        /// </summary>
        /// <param name="entity">Entidade a ser incluída.</param>
        /// <returns>Retorna a entidade após a inclusão. Apresentando algum erro retorna null.</returns>
        public TSource Add(TSource entity)
        {
            //Aguardando definir como será realizado a validação das entidades
            //if (!IsValid(entity))
            //    return default(TSource);

            try
            {
                Context.AddObject(Context.CreateObjectSet<TSource>().EntitySet.Name, entity);
                Context.SaveChanges();
                return entity;
            }
            catch (Exception ex)
            {
                if (ex.InnerException != null)
                    throw new Exception(ex.InnerException.Message, ex);
                throw new Exception(ex.Message, ex);
            }
        }

        /// <summary>
        /// Exclui a entidade indicada.
        /// </summary>
        /// <param name="entity">Entidade a ser deletada.</param>
        /// <returns>Retorna true se a entidade for excluido. Caso contrário retorna false.</returns>
        public bool Delete(TSource entity)
        {
            try
            {
                Context.AttachTo(Context.CreateObjectSet<TSource>().EntitySet.Name, entity);
                Context.DeleteObject(entity);
                Context.SaveChanges();
                return true;
            }
            catch (Exception ex)
            {
                if (ex.InnerException != null)
                    throw new Exception(ex.InnerException.Message, ex);
                throw new Exception(ex.Message, ex);
            }
        }

        /// <summary>
        /// Atualiza a entidade indicada.
        /// </summary>
        /// <param name="entity">Entidade a ser atualizada.</param>
        /// <returns>Retorna true se a entidade for atualizado. Caso contrário retorna false.</returns>
        public bool Update(TSource entity)
        {
            //Aguardando definir como será realizado a validação das entidades
            //if (!IsValid(entity))
            //    return false;

            try
            {
                //não precisa atachar pois antes de atleração sempre devemos buscar com GetByID
                //Context.AttachTo(Context.CreateObjectSet<TSource>().EntitySet.Name, entity);
                //if (Context.ObjectStateManager.GetObjectStateEntry(TSource).State.CreateObjectSet<TSource>()))
                Context.DetectChanges();
                Context.SaveChanges();
                return true;
            }
            catch (Exception ex)
            {
                if (ex.InnerException != null)
                    throw new Exception(ex.InnerException.Message, ex);
                throw new Exception(ex.Message, ex);
            }
        }

        /// <summary>
        /// Confere se os valores da entidade são válidos.
        /// </summary>
        /// <param name="entity">Entidade para conferência.</param>
        /// <returns>Retorna true se a entidade for válida. Caso contrário retorna false.</returns>
        public bool IsValid(TSource entity)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Adiciona a entidade indicada.
        /// </summary>
        /// <param name="entity">Entidade a ser incluída.</param>
        /// <returns>Retorna a entidade após a inclusão. Apresentando algum erro retorna null.</returns>
        public TSource AddWithDependents(TSource entity)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Exclui a entidade indicada e os registros dependetes.
        /// </summary>
        /// <param name="entity">Entidade a ser deletada.</param>
        /// <returns>Retorna true se a entidade for excluido. Caso contrário retorna false.</returns>
        public bool DeleteWithDependents(TSource entity)
        {
            throw new NotImplementedException();
        }
    }
}
